package com.jeesite.modules.stock.service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.jeesite.common.config.Global;
import com.jeesite.common.web.ResponseData;
import com.jeesite.common.web.ResponseDataList;
import com.jeesite.modules.stock.dto.vo.PatternDataVO;
import com.jeesite.modules.sys.entity.User;
import com.jeesite.modules.sys.utils.UserUtils;
import com.jeesite.modules.utils.NVL;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.jeesite.common.lang.StringUtils;
import com.jeesite.modules.stock.dao.GroupDailyDataDao;
import com.jeesite.modules.stock.dao.SectorMonthDao;
import com.jeesite.modules.stock.dao.SectorWeekDao;
import com.jeesite.modules.stock.dao.StockDailyDao;
import com.jeesite.modules.stock.dao.StockMonthDao;
import com.jeesite.modules.stock.dao.StockWeekDao;
import com.jeesite.modules.stock.dto.vo.KLineDataVO;
import com.jeesite.modules.stock.dto.vo.StockRsStrongVO;
import com.jeesite.modules.stock.entity.SectorDailyEntity;
import com.jeesite.modules.stock.entity.SectorMonthEntity;
import com.jeesite.modules.stock.entity.SectorWeekEntity;
import com.jeesite.modules.stock.entity.StockDailyEntity;
import com.jeesite.modules.stock.entity.StockMonthEntity;
import com.jeesite.modules.stock.entity.StockWeekEntity;

@Service
public class KlineDataService {

    @Autowired
    private StockDailyDao stockDailyDao;

    @Autowired
    private StockWeekDao stockWeekDao;

    @Autowired
    private StockMonthDao stockMonthDao;

    @Autowired
    private GroupDailyDataDao groupDailyDataDao;

    @Autowired
    private SectorWeekDao sectorWeekDao;

    @Autowired
    private SectorMonthDao sectorMonthDao;

    @Autowired
    private UserRequestLogsService userRequestLogsService;

    SimpleDateFormat fr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    SimpleDateFormat dateFr = new SimpleDateFormat("yyyy-MM-dd");

    protected Logger log = LoggerFactory.getLogger(KlineDataService.class);

    private KLineDataVO convert(StockDailyEntity stockDailyPO) {
        KLineDataVO kLineData = new KLineDataVO();
        kLineData.setClose(stockDailyPO.getClose());
        kLineData.setOpen(stockDailyPO.getOpen());
        kLineData.setHigh(stockDailyPO.getHigh());
        kLineData.setLow(stockDailyPO.getLow());
        kLineData.setTurnover(stockDailyPO.getTurnover());
        kLineData.setVolume(stockDailyPO.getVolume());
        kLineData.setStockName(stockDailyPO.getStockName());
        kLineData.setStockCode(stockDailyPO.getStockCode());
        kLineData.setExchange(stockDailyPO.getExchange());
        kLineData.setTurnoverRate(stockDailyPO.getTurnoverRate());
        kLineData.setRiseAndFall(stockDailyPO.getRiseAndFall());
        kLineData.setTimestamp(fr.format(stockDailyPO.getDate()));
        kLineData.setStockWeighted8dRS(stockDailyPO.getStockWeighted8dRS());
        kLineData.setStockWeighted12dRS(stockDailyPO.getStockWeighted12dRS());
        kLineData.setStockWeighted24dRS(stockDailyPO.getStockWeighted24dRS());
        kLineData.setStockWeighted44dRS(stockDailyPO.getStockWeighted44dRS());
        kLineData.setStockWeighted64dRS(stockDailyPO.getStockWeighted64dRS());
        kLineData.setStockWeighted128dRS(stockDailyPO.getStockWeighted128dRS());
        kLineData.setStockWeighted252dRS(stockDailyPO.getStockWeighted252dRS());
        kLineData.setH1(stockDailyPO.getH1());
        kLineData.setH1Date(stockDailyPO.getH1Date());
        kLineData.setL1(stockDailyPO.getL1());
        kLineData.setL1Date(stockDailyPO.getL1Date());

        kLineData.setIsVcp(stockDailyPO.getIsVcp());
        kLineData.setH2(stockDailyPO.getH2());
        kLineData.setH2Date(stockDailyPO.getH2Date());
        kLineData.setL2(stockDailyPO.getL2());
        kLineData.setL2Date(stockDailyPO.getL2Date());

        kLineData.setH3(stockDailyPO.getH3());
        kLineData.setH3Date(stockDailyPO.getH3Date());
        kLineData.setL3(stockDailyPO.getL3());
        kLineData.setL3Date(stockDailyPO.getL3Date());

        kLineData.setTriangleHigh1Date(stockDailyPO.getTriangleHigh1Date());
        kLineData.setTriangleLow1Date(stockDailyPO.getTriangleLow1Date());
        kLineData.setTriangleHigh3Date(stockDailyPO.getTriangleHigh3Date());
        kLineData.setTriangleLow3Date(stockDailyPO.getTriangleLow3Date());

        kLineData.setTriangleHigh1Price(stockDailyPO.getTriangleHigh1Price());
        kLineData.setTriangleLow1Price(stockDailyPO.getTriangleLow1Price());
        kLineData.setTriangleHigh3Price(stockDailyPO.getTriangleHigh3Price());
        kLineData.setTriangleLow3Price(stockDailyPO.getTriangleLow3Price());


        kLineData.setWedgeHigh1Date(stockDailyPO.getWedgeHigh1Date());
        kLineData.setWedgeLow1Date(stockDailyPO.getWedgeLow1Date());
        kLineData.setWedgeHigh3Date(stockDailyPO.getWedgeHigh3Date());
        kLineData.setWedgeLow3Date(stockDailyPO.getWedgeLow3Date());

        kLineData.setWedgeHigh1Price(stockDailyPO.getWedgeHigh1Price());
        kLineData.setWedgeLow1Price(stockDailyPO.getWedgeLow1Price());
        kLineData.setWedgeHigh3Price(stockDailyPO.getWedgeHigh3Price());
        kLineData.setWedgeLow3Price(stockDailyPO.getWedgeLow3Price());

        kLineData.setFlagPoleStartDate(stockDailyPO.getFlagPoleStartDate());
        kLineData.setFlagPoleStartPrice(stockDailyPO.getFlagPoleStartPrice());
        kLineData.setFlagPoleEndDate(stockDailyPO.getFlagPoleEndDate());
        kLineData.setFlagPoleEndPrice(stockDailyPO.getFlagPoleEndPrice());
        kLineData.setFlagLow1Date(stockDailyPO.getFlagLow1Date());
        kLineData.setFlagLow1Price(stockDailyPO.getFlagLow1Price());
        kLineData.setFlagLow2Date(stockDailyPO.getFlagLow2Date());
        kLineData.setFlagLow2Price(stockDailyPO.getFlagLow2Price());
        kLineData.setFlagHigh1Date(stockDailyPO.getFlagHigh1Date());
        kLineData.setFlagHigh1Price(stockDailyPO.getFlagHigh1Price());

        kLineData.setDbPreBottomMaxDate(stockDailyPO.getDbPreBottomMaxDate());
        kLineData.setDbPreBottomMaxHigh(stockDailyPO.getDbPreBottomMaxHigh());
        kLineData.setDbFirstBottomDate(stockDailyPO.getDbFirstBottomDate());
        kLineData.setDbFirstBottomPrice(stockDailyPO.getDbFirstBottomPrice());
        kLineData.setDbMiddlePeakDate(stockDailyPO.getDbMiddlePeakDate());
        kLineData.setDbMiddlePeakHigh(stockDailyPO.getDbMiddlePeakHigh());
        kLineData.setDbSecondBottomDate(stockDailyPO.getDbSecondBottomDate());
        kLineData.setDbSecondBottomPrice(stockDailyPO.getDbSecondBottomPrice());
        kLineData.setDbPostPatternMaxDate(stockDailyPO.getDbPostPatternMaxDate());
        kLineData.setDbPostPatternMaxHigh(stockDailyPO.getDbPostPatternMaxHigh());


        kLineData.setIhsLeftShoulderDate(stockDailyPO.getIhsLeftShoulderDate());
        kLineData.setIhsLeftShoulderPrice(stockDailyPO.getIhsLeftShoulderPrice());
        kLineData.setIhsLeftPeakDate(stockDailyPO.getIhsLeftPeakDate());
        kLineData.setIhsLeftPeakPrice(stockDailyPO.getIhsLeftPeakPrice());
        kLineData.setIhsHeadDate(stockDailyPO.getIhsHeadDate());
        kLineData.setIhsHeadPrice(stockDailyPO.getIhsHeadPrice());
        kLineData.setIhsRightPeakDate(stockDailyPO.getIhsRightPeakDate());
        kLineData.setIhsRightPeakPrice(stockDailyPO.getIhsRightPeakPrice());
        kLineData.setIhsRightShoulderDate(stockDailyPO.getIhsRightShoulderDate());
        kLineData.setIhsRightShoulderPrice(stockDailyPO.getIhsRightShoulderPrice());

        kLineData.setSupportDate1(stockDailyPO.getSupportDate1());
        kLineData.setSupportPrice1(stockDailyPO.getSupportPrice1());
        kLineData.setSupportDate2(stockDailyPO.getSupportDate2());
        kLineData.setSupportPrice2(stockDailyPO.getSupportPrice2());

        kLineData.setResistanceDate1(stockDailyPO.getResistanceDate1());
        kLineData.setResistancePrice1(stockDailyPO.getResistancePrice1());
        kLineData.setResistanceDate2(stockDailyPO.getResistanceDate2());
        kLineData.setResistancePrice2(stockDailyPO.getResistancePrice2());

        return kLineData;
    }

    private KLineDataVO convert(SectorDailyEntity sectorDailyEntity) {
        KLineDataVO kLineData = new KLineDataVO();
        kLineData.setClose(sectorDailyEntity.getClose());
        kLineData.setOpen(sectorDailyEntity.getOpen());
        kLineData.setHigh(sectorDailyEntity.getHigh());
        kLineData.setLow(sectorDailyEntity.getLow());
        kLineData.setTurnover(sectorDailyEntity.getTurnover());
        kLineData.setVolume(sectorDailyEntity.getVolume());
        kLineData.setStockName(sectorDailyEntity.getGroupName());
        kLineData.setTurnoverRate(sectorDailyEntity.getTurnoverRate());
        kLineData.setRiseAndFall(sectorDailyEntity.getRiseAndFall());
        kLineData.setTimestamp(fr.format(sectorDailyEntity.getDate()));
        return kLineData;
    }

    private KLineDataVO convert(SectorWeekEntity sectorWeekEntity) {
        KLineDataVO kLineData = new KLineDataVO();
        kLineData.setClose(sectorWeekEntity.getClose());
        kLineData.setOpen(sectorWeekEntity.getOpen());
        kLineData.setHigh(sectorWeekEntity.getHigh());
        kLineData.setLow(sectorWeekEntity.getLow());
        kLineData.setTurnover(sectorWeekEntity.getTurnover());
        kLineData.setVolume(sectorWeekEntity.getVolume());
        kLineData.setStockName(sectorWeekEntity.getGroupName());
        kLineData.setTurnoverRate(sectorWeekEntity.getTurnoverRate());
        kLineData.setRiseAndFall(sectorWeekEntity.getRiseAndFall());
        kLineData.setTimestamp(fr.format(sectorWeekEntity.getDate()));
        return kLineData;
    }

    private KLineDataVO convert(SectorMonthEntity sectorMonthEntity) {
        KLineDataVO kLineData = new KLineDataVO();
        kLineData.setClose(sectorMonthEntity.getClose());
        kLineData.setOpen(sectorMonthEntity.getOpen());
        kLineData.setHigh(sectorMonthEntity.getHigh());
        kLineData.setLow(sectorMonthEntity.getLow());
        kLineData.setTurnover(sectorMonthEntity.getTurnover());
        kLineData.setVolume(sectorMonthEntity.getVolume());
        kLineData.setStockName(sectorMonthEntity.getGroupName());
        kLineData.setTurnoverRate(sectorMonthEntity.getTurnoverRate());
        kLineData.setRiseAndFall(sectorMonthEntity.getRiseAndFall());
        kLineData.setTimestamp(fr.format(sectorMonthEntity.getDate()));
        return kLineData;
    }

    private void sortKlineData(List<KLineDataVO> klineDatas) {
        klineDatas.sort(Comparator.comparing(KLineDataVO::getTimestamp));
    }

    private String getNowDateFormat() {
        return dateFr.format(new Date());
    }

    /**
     * 获取日期偏移量
     *
     * @return 日期偏移量
     */
    private String getDateOffsetFormat() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, 365);
        return dateFr.format(calendar.getTime());
    }

    /**
     * 获取板块K线数据
     *
     * @param groupName 板块名称
     * @param period    时间单位
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return K线数据
     */
    public ResponseData<List<KLineDataVO>> getSectorKlineDatas(String groupName, String period, String startDate, String endDate) {
        try {
            reqLimit();
        } catch (IllegalStateException e) {
            return ResponseData.error(e.getMessage());
        }
        groupName = groupName.replace("!", "/");
        String _starDate = StringUtils.isBlank(startDate) ? getDateOffsetFormat() : startDate;
        String _endDate = StringUtils.isBlank(endDate) ? getNowDateFormat() : endDate;
        List<KLineDataVO> klineDatas = new ArrayList<>();
        if (Objects.equals(period, "Day")) {
            List<SectorDailyEntity> datas = groupDailyDataDao.findList(groupName, _starDate, _endDate);
            datas.removeIf(item -> item.getClose() == null);
            klineDatas = datas.stream().map(this::convert).collect(Collectors.toList());
            sortKlineData(klineDatas);
        } else if (Objects.equals(period, "Week")) {
            List<SectorWeekEntity> datas = sectorWeekDao.findList(groupName, startDate, _endDate);
            datas.removeIf(item -> item.getClose() == null);
            klineDatas = datas.stream().map(this::convert).collect(Collectors.toList());
            sortKlineData(klineDatas);
        } else if (Objects.equals(period, "Month")) {
            List<SectorMonthEntity> datas = sectorMonthDao.findList(groupName);
            datas.removeIf(item -> item.getClose() == null);
            klineDatas = datas.stream().map(this::convert).collect(Collectors.toList());
            sortKlineData(klineDatas);
        }
        // 添加请求记录
        User user = UserUtils.getUser();
        userRequestLogsService.addRequestLog(user.getId(), "KLINE_LIST");
        return ResponseData.success(klineDatas);
    }

    private void reqLimit() {
        User user = UserUtils.getUser();
        int minuteNr = userRequestLogsService.getRequestCountAfterTime(user.getId(),
                "KLINE_LIST",
                TimeUnit.MINUTES);

        int hourNr = userRequestLogsService.getRequestCountAfterTime(user.getId(),
                "KLINE_LIST",
                TimeUnit.HOURS);
        int minuteLimit = Integer.parseInt(NVL.emptyVl(Global.getConfig("KLINE_LIST_MINUTES_LIMIT"), "-1"));
        int housrLimit = Integer.parseInt(NVL.emptyVl(Global.getConfig("KLINE_LIST_HOUR_LIMIT"), "-1"));
        if (minuteLimit > -1 && minuteNr > minuteLimit) {
            RequestLimitCache.addLimit(UserUtils.getUser().getId());
        }
        if (housrLimit > -1 && hourNr > housrLimit) {
            RequestLimitCache.addLimit(UserUtils.getUser().getId());
        }
    }

    /**
     * 查询股票K线数据
     *
     * @param stockCode 股票代码
     * @param period    日(Day) 周(Week) 月(Month)
     */
    public ResponseData<List<KLineDataVO>> getStockKlineDatas(String stockCode, String period, String startDate, String endDate) {
        try {
            reqLimit();
        } catch (IllegalStateException e) {
            return ResponseData.error(e.getMessage());
        }
        String _starDate = StringUtils.isBlank(startDate) ? getDateOffsetFormat() : startDate;
        String _endDate = StringUtils.isBlank(endDate) ? getNowDateFormat() : endDate;
        List<KLineDataVO> klineDatas = new ArrayList<>();
        if (Objects.equals(period, "Day")) {
            List<StockDailyEntity> datas = stockDailyDao.getKlineDataOfDay(stockCode, _starDate, _endDate);
            klineDatas = datas.stream().map(this::convert).collect(Collectors.toList());
            sortKlineData(klineDatas);
        } else if (Objects.equals(period, "Week")) {
            List<StockWeekEntity> datas = stockWeekDao.getKlineDataOfDay(stockCode, _starDate, _endDate);
            klineDatas = datas.stream().map(item -> {
                KLineDataVO kline = new KLineDataVO();
                try {
                    BeanUtils.copyProperties(kline, item);
                    kline.setTimestamp(fr.format(item.getDate().getTime()));
                } catch (Exception e) {
                    log.error("查询周线数据失败,errMsg:{}", e.getMessage(), e);
                }
                return kline;
            }).collect(Collectors.toList());
            sortKlineData(klineDatas);
        } else if (Objects.equals(period, "Month")) {
            List<StockMonthEntity> datas = stockMonthDao.getKlineDataOfDay(stockCode);
            klineDatas = datas.stream().map(item -> {
                KLineDataVO kline = new KLineDataVO();
                try {
                    BeanUtils.copyProperties(kline, item);
                    kline.setTimestamp(fr.format(item.getDate().getTime()));
                } catch (Exception e) {
                    log.error("查询月线数据失败,errMsg:{}", e.getMessage(), e);
                }
                return kline;
            }).collect(Collectors.toList());
            sortKlineData(klineDatas);

        }
        // 添加请求记录
        User user = UserUtils.getUser();
        userRequestLogsService.addRequestLog(user.getId(), "KLINE_LIST");
        return ResponseData.success(klineDatas);
    }

    public PatternDataVO getStockPatternData(String stockCode) {
        Map<String, Object> data = stockDailyDao.getStockLatestData(stockCode);
        PatternDataVO vo = new PatternDataVO();
        vo.setH1(data.get("H1") == null ? -1d : ((BigDecimal) data.get("H1")).doubleValue());
        vo.setH2(data.get("H2") == null ? -1d : ((BigDecimal) data.get("H2")).doubleValue());
        vo.setH3(data.get("H3") == null ? -1d : ((BigDecimal) data.get("H3")).doubleValue());

        vo.setL1(data.get("L1") == null ? -1d : ((BigDecimal) data.get("L1")).doubleValue());
        vo.setL2(data.get("L2") == null ? -1d : ((BigDecimal) data.get("L2")).doubleValue());
        vo.setL3(data.get("L3") == null ? -1d : ((BigDecimal) data.get("L3")).doubleValue());


        vo.setH1Date(data.get("H1Date") == null ? null : (Date) data.get("H1Date"));
        vo.setH2Date(data.get("H2Date") == null ? null : (Date) data.get("H2Date"));
        vo.setH3Date(data.get("H3Date") == null ? null : (Date) data.get("H3Date"));

        vo.setL1Date(data.get("L1Date") == null ? null : (Date) data.get("L1Date"));
        vo.setL2Date(data.get("L2Date") == null ? null : (Date) data.get("L2Date"));
        vo.setL3Date(data.get("L3Date") == null ? null : (Date) data.get("L3Date"));

        return vo;
    }

    public List<StockRsStrongVO> getStockRsStrong(String stockCode, String period) {
        List<Map<String, Object>> list = stockDailyDao.getStockRsStrong(stockCode, period);
        List<StockRsStrongVO> vos = new ArrayList<>();

        List<StockRsStrongVO> stockRsList = new ArrayList<>();
        List<StockRsStrongVO> sectorRsList = new ArrayList<>();

        for (Map<String, Object> data : list) {
            StockRsStrongVO stockRs = new StockRsStrongVO();
            stockRs.setDate(data.get("date").toString());
            stockRs.setValue(Double.parseDouble(String.valueOf(data.get("value"))));
            stockRs.setCategory("个股");
            stockRsList.add(stockRs);

            StockRsStrongVO sectorRs = new StockRsStrongVO();
            sectorRs.setCategory("板块");
            sectorRs.setDate(data.get("date").toString());
            sectorRs.setValue(Double.parseDouble(String.valueOf(data.get("sectorValue"))));

            sectorRsList.add(sectorRs);
        }

        stockRsList.sort(Comparator.comparing(StockRsStrongVO::getDate));
        sectorRsList.sort(Comparator.comparing(StockRsStrongVO::getDate));

        List<Double> stockValues = stockRsList.stream().map(StockRsStrongVO::getValue).toList();
        List<Double> sectorValues = sectorRsList.stream().map(StockRsStrongVO::getValue).toList();
        for (StockRsStrongVO item : stockRsList) {
            item.setValue(BigDecimal.valueOf((item.getValue() - stockValues.getFirst()) * 100 / stockValues.getFirst())
                    .setScale(2, RoundingMode.HALF_UP).doubleValue());
        }

        for (StockRsStrongVO item : sectorRsList) {
            item.setValue(BigDecimal.valueOf((item.getValue() - sectorValues.getFirst()) * 100 / sectorValues.getFirst())
                    .setScale(2, RoundingMode.HALF_UP).doubleValue());
        }

        vos.addAll(stockRsList);
        vos.addAll(sectorRsList);

        return vos;
    }
}
